﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Reflection;

namespace StringVariables
{
    public abstract class VariableType
    {
        public abstract bool IsValid(string value);

        public virtual string ShortName
        {
            get
            {
                var name = this.GetType().Name;
                if (name.EndsWith("VariableType", StringComparison.OrdinalIgnoreCase))
                {
                    return name.Substring(0, name.Length - 12);
                }
                return name;
            }
        }

        public virtual string FullName
        {
            get
            {
                return this.GetType().FullName;
            }
        }
        #region static
        public static VariableType<string> String
        {
            get
            {
                return new StringVariableType();
            }
        }
        public static VariableType<int> Int
        {
            get
            {
                return new IntVariableType();
            }
        }
        public static VariableType<bool> Bool
        {
            get
            {
                return new BoolVariableType();
            }
        }
        public static VariableType<double> Real
        {
            get
            {
                return new RealVariableType();
            }
        }

        private static List<VariableType> _types;

        public static IEnumerable<VariableType> List
        {
            get
            {
                return _types ?? (_types = GetTypes());
            }
        }

        private static List<VariableType> GetTypes()
        {
            var ass = AppDomain.CurrentDomain.GetAssemblies();
            var vt = typeof(VariableType);
            return ass.SelectMany(a =>
                {
                    try
                    {
                        return a.GetTypes();
                    }
                    catch { return new Type[] { }; }
                }
                ).Where(t => vt.IsAssignableFrom(t) && !t.IsAbstract).Select(t => Activator.CreateInstance(t) as VariableType).Where(t => t != null).ToList();
        }
        public static VariableType Parse(string value)
        {
            //!!!  WARNING  !!!
            //  !  DANGER   !
            //  ! ugly code !
            value = value.ToLower();
            switch (value)
            {
                case "string": return String;
                case "int": return Int;
                case "bool": return Bool;
                case "real": return Real;
            }
            var name = new string(value.TakeWhile(ch => ch != '(').ToArray());
            name.Trim();
            int bracket = 0;
            //set parameters from brackets
            var parameters = new string(
                value.SkipWhile(ch => ch != '(')
                .TakeWhile(ch =>
                {
                    if (ch == '(') bracket++;
                    else if (ch == ')')
                        if (bracket == 0) return false;
                        else bracket--;
                    return true;
                }).ToArray());
            var vt = typeof(VariableType);
            //find type in assemblies
            var foundType = AppDomain.CurrentDomain.GetAssemblies()
                .SelectMany(s => s.GetTypes())
                .Where(p => vt.IsAssignableFrom(p))
                .FirstOrDefault(t => t.Name.ToLower() == name || t.Name.ToLower() == name + "variabletype" || t.FullName.ToLower() == name);
            if (foundType == null) throw new InvalidOperationException("type of entered name does not exist");
            //construct it
            var constuctors = foundType.GetConstructors().Where(c => !c.IsStatic && c.IsPublic);
            var parametrizedConstruct = constuctors.FirstOrDefault(c => c.GetParameters().Count() == 1 && c.GetParameters().FirstOrDefault().ParameterType == typeof(string));
            if (parametrizedConstruct != null)
            {
                return parametrizedConstruct.Invoke(new object[] { parameters }) as VariableType;
            }
            var nonParameterConstruct = constuctors.FirstOrDefault(c => c.GetParameters().Count() == 0);
            if (nonParameterConstruct != null)
            {
                return nonParameterConstruct.Invoke(new object[] { }) as VariableType;
            }
            throw new InvalidOperationException("can't call constructor");
        }

        public static bool TryParse(string value, out VariableType vt)
        {
            vt = null;
            try
            {
                vt = Parse(value);
                return true;
            }
            catch
            {
                return false;
            }
        }
        #endregion

    }
    public abstract class VariableType<T> : VariableType
    {
        public virtual T GetValue(string value)
        {
            if (IsValid(value))
            {
                return GetValueInternal(value);
            }
            throw new ArgumentException("invalid value");
        }
        protected abstract T GetValueInternal(string value);
    }
}
